
#include <material/materialmanager.h>
#include <material/io/materialxmlexporter.h>
#include <helper.h>
#include <session.h>

#include <qdir.h>
#include <qdiriterator.h>
#include <qimage.h>

#define CCE_DEPLOYMENT_SUBDIR "deployment/"
#define CCE_TEXTURE_SUBDIR	"textures/"

cceMaterialXMLExporter::cceMaterialXMLExporter(cceMaterialManager *manager)
	: _manager(manager)
{
}

void cceMaterialXMLExporter::ExportAll()
{

	ExportTextures ();
  ExportShaders ();
  ExportPrograms ();
  ExportEffects();
  ExportMaterials ();

  ExportResources ();
}


/* *******************************
 *   Export textures
 * *******************************/

void cceMaterialXMLExporter::ExportTextures()
{
	QDomDocument doc ("textures");

	ExportTextures(doc);

	PrepareDeploymentPath (CCE_TEXTURE_SUBDIR);
  QString outputFilename = cceSession::Get()->GetFileName(QString(CCE_DEPLOYMENT_SUBDIR) + "textures.xtxts");

	QFile file (outputFilename);
	if (file.open(QIODevice::WriteOnly))
		{
			file.write(doc.toByteArray(4));
			file.close();
		}

	foreach (QString textureFile, _textureNames)
		{
			QString src = cceSession::Get()->GetFileName(QString ("textures/") + textureFile);
			QString dst = cceSession::Get()->GetFileName(QString (CCE_DEPLOYMENT_SUBDIR) + QString(CCE_TEXTURE_SUBDIR) + textureFile);
			if (!QFile::copy(src, dst))
				{
					qDebug () << QString ("Copy ('%1', '%2'); Failed\n").arg (src).arg(dst);
				}
		}
}

void cceMaterialXMLExporter::ExportTextures(QDomDocument &doc)
{
	_textureNames.clear();

	QDomElement texturesElement = doc.createElement("textures");
	doc.appendChild(texturesElement);

	for (unsigned i=0, numTextures=_manager->GetNumberOfTextures();
			 i<numTextures;
			 ++i)
		{
			cceMaterialTexture *texture = _manager->GetTexture(i);
			ExportTexture(doc, texturesElement, texture);
		}


}

void cceMaterialXMLExporter::ExportTexture(QDomDocument &doc, QDomElement &parent, cceMaterialTexture *texture)
{
	QString filename = cceSession::Get()->GetFileName(QString(CCE_TEXTURE_SUBDIR) + texture->GetFilename());
	QImage img (filename);
	if (img.isNull())
		{
			// this image is not real so don't export the texture
			return;
		}
	QImage::Format format = img.format();


	QString formatString;
	switch (format)
		{
		case QImage::Format_RGB32:
			formatString = "R8G8B8";
			break;
		case QImage::Format_ARGB32:
			formatString = "R8G8B8A8";
			break;
		default:
			// this image has an unsupported image format
			return;
		}

	QString wrapString;
	switch (texture->GetWrap())
		{
		case cceMaterialTexture::Wrap_Clamp:
			wrapString = "ClampToEdge";
			break;
		case cceMaterialTexture::Wrap_Repeat:
			wrapString = "Repeat";
			break;
		default:
			// this is an unsupported wrap mode
			return;
		}

	// store the filename, we will copy them later into the final location
	_textureNames.insert(texture->GetFilename());

	// all values are evaluated so assemble the xml

	QDomElement textureElement = doc.createElement("texture");
	textureElement.setAttribute("type", "texture2d");
	textureElement.setAttribute("name", texture->GetName().trimmed());

	QDomElement filenameElement = doc.createElement("filename");
	filenameElement.appendChild(doc.createTextNode((QString(CCE_TEXTURE_SUBDIR) + texture->GetFilename()).trimmed()));
	textureElement.appendChild(filenameElement);

	QDomElement formatElement = doc.createElement ("format");
	formatElement.appendChild(doc.createTextNode(formatString));
	textureElement.appendChild(formatElement);

	QDomElement wrapElement = doc.createElement("wrap");
	wrapElement.appendChild(doc.createTextNode(wrapString));
	textureElement.appendChild(wrapElement);


	parent.appendChild(textureElement);
}


void cceMaterialXMLExporter::PrepareDeploymentPath(const QString& subDirectory, bool clear)
{
	QDir (cceSession::Get()->GetFileName("")).mkdir(CCE_DEPLOYMENT_SUBDIR);


	QString deploymentPath = cceSession::Get()->GetFileName(CCE_DEPLOYMENT_SUBDIR);

	QDir deploymentDir (deploymentPath);
	if (deploymentDir.exists(subDirectory))
		{
			if (!clear)
				{
					printf ("no clear\n"); fflush(stdout);
					return;
				}

			QDir subPath (cceSession::Get()->GetFileName(QString (CCE_DEPLOYMENT_SUBDIR) + subDirectory));
			cceDirRemoverHelper::RecursiveRemoveDir (subPath);
		}

	deploymentDir.mkdir(CCE_TEXTURE_SUBDIR);
}


/* *******************************
 *   Export shaders
 * *******************************/

void cceMaterialXMLExporter::ExportShaders()
{
  QDomDocument doc ("shaders");

  ExportShaders(doc);

  QString outputFilename = cceSession::Get()->GetFileName(QString(CCE_DEPLOYMENT_SUBDIR) + "shaders.xshds");

  QFile file (outputFilename);
  if (file.open(QIODevice::WriteOnly))
    {
      file.write(doc.toByteArray(4));
      file.close();
    }
}



void cceMaterialXMLExporter::ExportShaders(QDomDocument &doc)
{
  QDomElement shadersElement = doc.createElement("shaders");
  doc.appendChild(shadersElement);

  for (unsigned i=0, numShaders=_manager->GetNumberOfShaders();
       i<numShaders;
       ++i)
    {
      cceMaterialShader *shader = _manager->GetShader(i);
      ExportShader(doc, shadersElement, shader);
    }
}

void cceMaterialXMLExporter::ExportShader(QDomDocument &doc, QDomElement &parent, cceMaterialShader *shader)
{
  QDomElement shaderElement = doc.createElement("shader");
  shaderElement.setAttribute("name", shader->GetName());


  QDomElement typeElement = doc.createElement("type");
  QString typeString = "";
  switch (shader->GetType())
    {
    case cceMaterialShader::Type_Vertex:
      typeString = "Vertex";
      break;
    case cceMaterialShader::Type_Fragment:
      typeString = "Fragment";
      break;
    case cceMaterialShader::Type_Geometry:
      typeString = "Geometry";
      break;
    case cceMaterialShader::Type_TesselationControl:
      typeString = "TesselationControl";
      break;
    case cceMaterialShader::Type_TesselationEval:
      typeString = "TesselationEvaluation";
      break;
    default:
      return;
    }
  typeElement.appendChild(doc.createTextNode(typeString));
  shaderElement.appendChild(typeElement);


  QDomElement codeElement = doc.createElement("code");
  codeElement.appendChild(doc.createCDATASection(shader->GetExportCode()));
  shaderElement.appendChild(codeElement);

  parent.appendChild(shaderElement);

}



/* *******************************
 *   Export program
 * *******************************/

void cceMaterialXMLExporter::ExportPrograms()
{
  QDomDocument doc ("programs");

  ExportPrograms(doc);

  QString outputFilename = cceSession::Get()->GetFileName(QString(CCE_DEPLOYMENT_SUBDIR) + "programs.xprgs");

  QFile file (outputFilename);
  if (file.open(QIODevice::WriteOnly))
    {
      file.write(doc.toByteArray(4));
      file.close();
    }
}

void cceMaterialXMLExporter::ExportPrograms(QDomDocument &doc)
{
  QDomElement programsElement = doc.createElement("programs");
  doc.appendChild(programsElement);

  for (unsigned i=0, numPrograms=_manager->GetNumberOfPrograms();
       i<numPrograms;
       ++i)
    {
      cceMaterialProgram *program= _manager->GetProgram(i);
      ExportProgram (doc, programsElement, program);
    }
}

namespace
{
QString get_max_version_string (cceMaterialProgramTechnique* technique)
{
  unsigned v = 110;
  for (unsigned i=0, j=technique->GetNumberOfShaders(); i<j; ++i)
    {
      cceMaterialShader* shader = technique->GetShader(i);
      QString vs = shader->GetVersionString();

      bool ok;
      int versionInt = vs.toInt(&ok);
      if (ok)
        {
          v = versionInt > v ? versionInt : v;
        }
    }

  return QString ("%1").arg(v);
}

}

void cceMaterialXMLExporter::ExportProgram(QDomDocument &doc, QDomElement &parent, cceMaterialProgram *program)
{
  QDomElement programElement = doc.createElement("program");
  programElement.setAttribute("name", program->GetName());

  for (unsigned i=0, numTechniques=program->GetNumberOfTechniques();
       i<numTechniques;
       ++i)
    {
      cceMaterialProgramTechnique* technique = program->GetTechnique(i);
      unsigned numShaders = technique->GetNumberOfShaders();

      QString version = get_max_version_string(technique);

      QDomElement shaderProgramElement = doc.createElement("shaderprogram");

      QDomElement requirementsElement = doc.createElement("requirements");
      QDomElement requirementElement = doc.createElement("requirement");
      QDomElement versionElement = doc.createElement("version");
      versionElement.appendChild(doc.createTextNode(version));
      requirementElement.appendChild(versionElement);
      requirementsElement.appendChild(requirementElement);
      shaderProgramElement.appendChild(requirementsElement);

      for (unsigned j=0; j<numShaders; ++j)
        {
          cceMaterialShader* shader = technique->GetShader(j);
          QDomElement shaderElement = doc.createElement("shader");
          shaderElement.setAttribute("name", shader->GetName());
          shaderProgramElement.appendChild(shaderElement);
        }


      QDomElement bindingsElement = doc.createElement("bindings");
      for (unsigned j=0; j<numShaders; ++j)
        {
          cceMaterialShader* shader = technique->GetShader(j);
          for (unsigned k=0, numBindings=shader->GetNumberOfBindings();
               k<numBindings;
               ++k)
            {
              QString semantic = shader->GetBindingSemantic(k);
              QString variable = shader->GetBindingVariable(k);

              QDomElement bindingElement = doc.createElement("binding");
              bindingElement.setAttribute("binding", semantic);
              bindingElement.setAttribute("name", variable);
              bindingsElement.appendChild(bindingElement);
            }
        }
      shaderProgramElement.appendChild(bindingsElement);

      programElement.appendChild(shaderProgramElement);

    }

  parent.appendChild(programElement);
}





/* *******************************
 *   Export effect
 * *******************************/

void cceMaterialXMLExporter::ExportEffects()
{
  QDomDocument doc ("effects");

  ExportEffects(doc);

  QString outputFilename = cceSession::Get()->GetFileName(QString(CCE_DEPLOYMENT_SUBDIR) + "effects.xfxs");

  QFile file (outputFilename);
  if (file.open(QIODevice::WriteOnly))
    {
      file.write(doc.toByteArray(4));
      file.close();
    }
}

void cceMaterialXMLExporter::ExportEffects(QDomDocument &doc)
{
  QDomElement effectsElement = doc.createElement("effects");
  doc.appendChild(effectsElement);

  for (unsigned i=0, numEffects=_manager->GetNumberOfEffects();
       i<numEffects;
       ++i)
    {
      cceMaterialEffect *effect= _manager->GetEffect(i);
      ExportEffect (doc, effectsElement, effect);
    }
}


void cceMaterialXMLExporter::ExportEffect(QDomDocument &doc, QDomElement &parent, cceMaterialEffect *effect)
{
  QDomElement effectElement = doc.createElement("effect");
  effectElement.setAttribute("name", effect->GetName());

  QDomElement solverElement = doc.createElement("solver");
  solverElement.appendChild(doc.createTextNode(""));
  effectElement.appendChild(solverElement);

  for (unsigned i=0, numStages=effect->GetNumberOfStages();
       i<numStages;
       ++i)
    {
      cceMaterialEffectStage *stage = effect->GetStage(i);

      QDomElement effectProgramElement = doc.createElement("effectprogram");

      for (unsigned j=0; j<Pass_COUNT; ++j)
        {
          cceMaterialPass pass = (cceMaterialPass)j;
          cceMaterialProgram *program = stage->GetPass(pass);

          if (program)
            {
              QDomElement programElement = doc.createElement("program");
              programElement.setAttribute("pass", cceMaterialEffectStage::GetPassName(pass));
              programElement.setAttribute("name", program->GetName());
              effectProgramElement.appendChild(programElement);
            }
        }

      effectElement.appendChild(effectProgramElement);
    }
  parent.appendChild(effectElement);
}




/* *******************************
 *   Export materials
 * *******************************/

void cceMaterialXMLExporter::ExportMaterials()
{
  QDomDocument doc ("materials");

  ExportMaterials(doc);

  QString outputFilename = cceSession::Get()->GetFileName(QString(CCE_DEPLOYMENT_SUBDIR) + "materials.xmats");

  QFile file (outputFilename);
  if (file.open(QIODevice::WriteOnly))
    {
      file.write(doc.toByteArray(4));
      file.close();
    }

}

void cceMaterialXMLExporter::ExportMaterials(QDomDocument &doc)
{
  QDomElement materialsElement = doc.createElement("materials");
  doc.appendChild(materialsElement);

  for (unsigned i=0, numMaterials=_manager->GetNumberOfMaterials();
       i<numMaterials;
       ++i)
    {
      cceMaterialMaterial *material = _manager->GetMaterial(i);
      ExportMaterial(doc, materialsElement, material);
    }
}

namespace
{
QDomElement create_color_element (QDomDocument doc, const QString& name, const QColor &color)
{
  QDomElement element = doc.createElement(name);

  QString redV; redV.sprintf("%f", color.redF());
  QString greenV; greenV.sprintf("%f", color.greenF());
  QString blueV; blueV.sprintf("%f", color.blueF());
  QString alphaV; alphaV.sprintf("%f", color.alphaF());
  element.setAttribute("r", redV);
  element.setAttribute("g", greenV);
  element.setAttribute("b", blueV);
  element.setAttribute("a", alphaV);

  return element;
}

bool is_texture_required (QList<cceEffectTextureBindings> &bindings, cceMaterialPass pass, unsigned unit)
{
  foreach (cceEffectTextureBindings binding, bindings)
    {
      if (binding.Pass == pass && binding.Unit == unit)
        {
          return binding.Bindings.size() > 0;
        }
    }
  return false;
}

}

void cceMaterialXMLExporter::ExportMaterial(QDomDocument &doc, QDomElement &parent, cceMaterialMaterial *material)
{
  cceMaterialEffect *effect = material->GetEffect();
  if (!effect)
    {
      // a material without an effect is not possible so don't export it
      return;
    }

  QDomElement materialElement = doc.createElement("material");
  materialElement.setAttribute("name", material->GetName());

  QDomElement defaultElement = doc.createElement("default");
  defaultElement.appendChild(doc.createTextNode("false"));
  materialElement.appendChild(defaultElement);

  QDomElement specElement = doc.createElement("spec");
  specElement.appendChild(create_color_element (doc, "ambient", material->GetAmbient()));
  specElement.appendChild(create_color_element (doc, "diffuse", material->GetDiffuse()));
  specElement.appendChild(create_color_element (doc, "specular", material->GetSpecular()));

  QDomElement shininessElement = doc.createElement("shininess");
  shininessElement.appendChild(doc.createTextNode(QString::number(material->GetShininess(), 'f')));
  specElement.appendChild(shininessElement);

  specElement.appendChild(create_color_element (doc, "emission", material->GetEmission()));

  QDomElement writeDepthElement = doc.createElement("writedepth");
  writeDepthElement.appendChild(doc.createTextNode(material->IsWriteDepth() ? "true" : "false"));
  specElement.appendChild(writeDepthElement);

  QDomElement testDepthElement = doc.createElement("testdepth");
  testDepthElement.appendChild(doc.createTextNode(material->IsTestDepth() ? "true" : "false"));
  specElement.appendChild(testDepthElement);

  QDomElement transparencyElement = doc.createElement("transparency");
  transparencyElement.appendChild(doc.createTextNode(material->IsTransparent() ? "true" : "false"));
  specElement.appendChild(transparencyElement);

  materialElement.appendChild(specElement);


  QDomElement effectElement = doc.createElement("effect");
  effectElement.setAttribute("name", effect->GetName());
  materialElement.appendChild(effectElement);

  QList<cceEffectTextureBindings> bindings = effect->GetTextureRequirements();

  QDomElement texturepackElement = doc.createElement("texturepack");
  for (unsigned i=0; i<Pass_COUNT; ++i)
    {
      cceMaterialPass pass = (cceMaterialPass)i;
      QDomElement texturesElement = doc.createElement("textures");
      texturesElement.setAttribute("pass", cceMaterialEffectStage::GetPassName(pass));

      for (unsigned j=0; j<16; ++j)
        {
          cceMaterialTexture* texture = material->GetTexture(pass, j);
          if (texture && is_texture_required(bindings, pass, j))
            {
              QDomElement textureElement = doc.createElement("texture");
              textureElement.setAttribute("stage", j);
              textureElement.setAttribute("name", texture->GetName());
              texturesElement.appendChild(textureElement);
            }
        }

      texturepackElement.appendChild(texturesElement);
    }

  materialElement.appendChild(texturepackElement);


  parent.appendChild(materialElement);
}


namespace
{
QDomElement create_resource (QDomDocument doc, QString type, QString def)
{
  QDomElement element = doc.createElement("resources");
  element.setAttribute("type", type);
  element.setAttribute("default", def);
  return element;
}
}
void cceMaterialXMLExporter::ExportResources()
{
  QDomDocument doc ("resourcelocation");

  QDomElement resourcelocationElement = doc.createElement("resourcelocation");
  resourcelocationElement.appendChild(create_resource(doc, "texture",   "textures.xtxts"));
  resourcelocationElement.appendChild(create_resource(doc, "shader",    "shaders.xshds"));
  resourcelocationElement.appendChild(create_resource(doc, "program",   "programs.xprgs"));
  resourcelocationElement.appendChild(create_resource(doc, "effect",    "effects.xfxs"));
  resourcelocationElement.appendChild(create_resource(doc, "material",  "materials.xmats"));
  doc.appendChild(resourcelocationElement);


  QString outputFilename = cceSession::Get()->GetFileName(QString(CCE_DEPLOYMENT_SUBDIR) + "resources.xml");

  QFile file (outputFilename);
  if (file.open(QIODevice::WriteOnly))
    {
      file.write(doc.toByteArray(4));
      file.close();
    }
}
